﻿using System;
using System.Windows;

namespace Gmantis.Controls.Chart.Extended
{
    public enum FitType
    {
        Polynom,
        Exponent,
        Logarithmic,
        Power,
        Fourier,
        MinX,
        MinY,
        MaxX,
        MaxY,
        AverageX,
        AverageY
    }

    public class TrendLine : FunctionSeries
    {
        // Fields
        private double[] _coeffs;
        public static readonly DependencyProperty FitTypeProperty = DependencyProperty.Register("FitType", typeof(FitType), typeof(TrendLine), new PropertyMetadata(FitType.Polynom, new PropertyChangedCallback(FunctionSeries.OnDPChanged)));
        public static readonly DependencyProperty OrderProperty = DependencyProperty.Register("Order", typeof(int), typeof(TrendLine), new PropertyMetadata(2, new PropertyChangedCallback(TrendLine.OnOrderChanged)));

        // Methods
        public TrendLine()
        {
            base.Min = double.NaN;
            base.Max = double.NaN;
            base.Symbol = null;
            base.Connection = Utils.CreatePETemplate(typeof(Lines));
            base.ConnectionStrokeThickness = 2.0;
        }

        public double Aproximate(double value)
        {
            if (this._coeffs == null)
            {
                base.FireException("Regression coefficients are not calculated.");
                return double.NaN;
            }
            return LeastSquaresMethod.Aproximate(this.FitType, this._coeffs, value);
        }

        protected static void OnOrderChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            int newValue = (int)args.NewValue;
            if (newValue < 1)
            {
                throw new ArgumentOutOfRangeException("Order must be greater than 1.");
            }
            ((TrendLine)obj).UpdateData();
        }

        protected override double[,] ProcessValues(double[,] values)
        {
            if (values != null)
            {
                if ((((this.FitType == FitType.AverageX) || (this.FitType == FitType.AverageY)) || ((this.FitType == FitType.MinX) || (this.FitType == FitType.MinY))) || ((this.FitType == FitType.MaxX) || (this.FitType == FitType.MaxY)))
                {
                    return this.ProcessValuesSimple(values);
                }
                int length = values.GetLength(1);
                double[] x = new double[length];
                double[] y = new double[length];
                double maxValue = double.MaxValue;
                double minValue = double.MinValue;
                for (int i = 0; i < length; i++)
                {
                    x[i] = values[1, i];
                    if (x[i] < maxValue)
                    {
                        maxValue = x[i];
                    }
                    if (x[i] > minValue)
                    {
                        minValue = x[i];
                    }
                    y[i] = values[0, i];
                }
                int nt = Math.Min(this.Order, length);
                int sampleCount = base.SampleCount;
                this._coeffs = new double[nt];
                if (!base.ThrowException)
                {
                    try
                    {
                        LeastSquaresMethod.SolveLeastSquares(length, x, y, nt, this._coeffs, this.FitType, false);
                    }
                    catch (Exception)
                    {
                    }
                }
                else
                {
                    LeastSquaresMethod.SolveLeastSquares(length, x, y, nt, this._coeffs, this.FitType, false);
                }
                if (!double.IsNaN(base.Min))
                {
                    maxValue = base.Min;
                }
                if (!double.IsNaN(base.Max))
                {
                    minValue = base.Max;
                }
                double num7 = (minValue - maxValue) / ((double)(sampleCount - 1));
                values = new double[2, sampleCount];
                for (int j = 0; j < sampleCount; j++)
                {
                    double xvalue = maxValue + (num7 * j);
                    double num10 = LeastSquaresMethod.Aproximate(this.FitType, this._coeffs, xvalue);
                    values[1, j] = xvalue;
                    values[0, j] = num10;
                }
            }
            return values;
        }

        private double[,] ProcessValuesSimple(double[,] values)
        {
            int length = values.GetLength(1);
            double maxValue = double.MaxValue;
            double minValue = double.MinValue;
            double num4 = double.MaxValue;
            double num5 = double.MinValue;
            double num6 = 0.0;
            double num7 = 0.0;
            for (int i = 0; i < length; i++)
            {
                double num9 = values[1, i];
                if (num9 < maxValue)
                {
                    maxValue = num9;
                }
                if (num9 > minValue)
                {
                    minValue = num9;
                }
                num6 += num9;
                double num10 = values[0, i];
                if (num10 < num4)
                {
                    num4 = num10;
                }
                if (num10 > num5)
                {
                    num5 = num10;
                }
                num7 += num10;
            }
            num6 /= (double)length;
            num7 /= (double)length;
            values = new double[2, 2];
            switch (this.FitType)
            {
                case FitType.MinX:
                    values[1, 0] = maxValue;
                    values[1, 1] = maxValue;
                    values[0, 0] = num4;
                    values[0, 1] = num5;
                    return values;

                case FitType.MinY:
                    values[1, 0] = maxValue;
                    values[1, 1] = minValue;
                    values[0, 0] = num4;
                    values[0, 1] = num4;
                    return values;

                case FitType.MaxX:
                    values[1, 0] = minValue;
                    values[1, 1] = minValue;
                    values[0, 0] = num4;
                    values[0, 1] = num5;
                    return values;

                case FitType.MaxY:
                    values[1, 0] = maxValue;
                    values[1, 1] = minValue;
                    values[0, 0] = num5;
                    values[0, 1] = num5;
                    return values;

                case FitType.AverageX:
                    values[1, 0] = num6;
                    values[1, 1] = num6;
                    values[0, 0] = num4;
                    values[0, 1] = num5;
                    return values;

                case FitType.AverageY:
                    values[1, 0] = maxValue;
                    values[1, 1] = minValue;
                    values[0, 0] = num7;
                    values[0, 1] = num7;
                    return values;
            }
            return values;
        }

        public override void UpdateData()
        {
            base.Invalidate();
        }

        // Properties
        public double[] Coefficients
        {
            get
            {
                if (this._coeffs == null)
                {
                    ((IDataSeriesInfo)this).GetValues();
                }
                if (this._coeffs == null)
                {
                    return null;
                }
                return (double[])this._coeffs.Clone();
            }
        }

        public FitType FitType
        {
            get
            {
                return (FitType)base.GetValue(FitTypeProperty);
            }
            set
            {
                base.SetValue(FitTypeProperty, value);
            }
        }

        public int Order
        {
            get
            {
                return (int)base.GetValue(OrderProperty);
            }
            set
            {
                base.SetValue(OrderProperty, value);
            }
        }
    }
}
